/****************************************************************************
 * Chariott - Continuous Integration Testing Tool
 * Zaid Mohsin
 * The University of York
 * 2012/2013
 * MSc in Information Technology
 * All rights reserved.
 ****************************************************************************/
package chariott.main;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import chariott.configuration.Globals;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.SystemPropertiesCredentialsProvider;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.CreateTagsRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.RunInstancesRequest;
import com.amazonaws.services.ec2.model.RunInstancesResult;
import com.amazonaws.services.ec2.model.Tag;
import com.amazonaws.services.ec2.model.TerminateInstancesRequest;

/**
 * The class that provides connectivity to Amazon EC2 and notify
 * ChariottLauncher if any nodes started.
 * 
 * @author Zaid
 * 
 */
public class AWSProvider implements CloudConnectable {
	public static AmazonEC2 ec2;

	public List<PropertyChangeListener> listeners = new ArrayList<PropertyChangeListener>();
	private Map<String, String> instancesSubset;
	private int nodeIndex = 0;

	/**
	 * method to run the Amazon EC2 instances specified in Globals configuration
	 * notify ChariottLauncher if any instance is running.
	 * 
	 * 
	 */
	@Override
	public Map<String, String> runInstances(int numberOfInstances) {

		HashMap<String, String> myInstancesMap = new HashMap<String, String>();

		RunInstancesRequest runInstancesRequest = new RunInstancesRequest()
				.withImageId(Globals.AWS_AMI).withMinCount(numberOfInstances)
				.withMaxCount(numberOfInstances)
				.withSecurityGroupIds(Globals.AWS_SECURITY_GROUP)
				.withKeyName(Globals.AWS_KEY_NAME);

		RunInstancesResult runInstances = ec2.runInstances(runInstancesRequest);

		// TAG EC2 INSTANCES
		Reservation myReservation = runInstances.getReservation();

		List<Instance> instances = runInstances.getReservation().getInstances();

		HashMap<String, String> instanceMap = new HashMap<String, String>();
		ArrayList<String> pendingInstanceIDs = new ArrayList<String>();

		int tagCounter = 0;
		for (Instance i : instances) {

			pendingInstanceIDs.add(i.getInstanceId());

			// create a tag for the instance
			tagCounter++;
			Tag t = new Tag();
			t.setKey("Chariott Test");
			t.setValue(Integer.toString(tagCounter));

			List<Tag> tags = new ArrayList<Tag>();
			tags.add(t);
			// create a tag Request to submit to EC2
			CreateTagsRequest ctr = new CreateTagsRequest();
			ctr.setTags(tags);
			ctr.withResources(i.getInstanceId());
			ec2.createTags(ctr);
		}

		int numOfInstancesPending = pendingInstanceIDs.size();

		DescribeInstancesResult describeInstancesRequest = ec2
				.describeInstances();

		ArrayList<Instance> runningInstances = new ArrayList<Instance>();

		// wait for all instances to run add add them to the Array of running
		// instances.

		Main.logger.info("Initializing cloud nodes");
		while (numOfInstancesPending > 0) {
			for (Reservation r : ec2.describeInstances().getReservations()) {
				for (Instance i : r.getInstances()) {
					if (pendingInstanceIDs.contains(i.getInstanceId())) {

						if (i.getState().getCode() == 16
								&& !runningInstances.contains(i)) {
							// decrement the number of instances pending
							numOfInstancesPending--;

							// add the running instance to the list
							runningInstances.add(i);
							myInstancesMap.put(i.getInstanceId(),
									i.getPublicDnsName());

							// notify observers that the node is running
							notifyListeners(nodeIndex, i.getPublicDnsName(),
									"pending", i.getState().getName());

							// increment the node index
							nodeIndex++;

						}

					}

				}
			}
		}

		Main.logger.info("All Nodes are Running!");

		instancesSubset = myInstancesMap;

		return myInstancesMap;

	}

	/**
	 * kills all instances if necessary, should be called if anything interrupts
	 * ChARIOTT to launch
	 * 
	 */
	@Override
	public void stopInstances() {

		for (String instanceID : instancesSubset.keySet()) {
			// terminate the instance after the thread is done
			TerminateInstancesRequest tr = new TerminateInstancesRequest()
					.withInstanceIds(instanceID);
			ec2.terminateInstances(tr);
		}

	}

	/**
	 * connect to AWS EC2 client using the credentials provided by the
	 * configurations
	 * 
	 */
	@Override
	public void clientConnect() {
		AWSCredentialsProvider credentialsProvider = new SystemPropertiesCredentialsProvider();

		ec2 = new AmazonEC2Client(credentialsProvider);

		// set the endpoint if it is set
		if (!Globals.AWS_ENDPOINT.equals(null))
			ec2.setEndpoint(Globals.AWS_ENDPOINT);

	}

	/**
	 * add a listener to this object to check for notifications
	 * 
	 */
	@Override
	public void addChangeListener(PropertyChangeListener newListener) {
		listeners.add(newListener);

	}

	/**
	 * sent a notification for all listeners if an event occured such as a
	 * cloude node started running
	 * 
	 * @param obj
	 * @param hostname
	 * @param newStatus
	 * @param oldStatus
	 */
	public void notifyListeners(Object nodeNum, String hostname,
			String newStatus, String oldStatus) {
		for (PropertyChangeListener listener : listeners) {
			listener.propertyChange(new PropertyChangeEvent(nodeIndex,
					hostname, newStatus, oldStatus));
		}
	}

}
